package com.util;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
/**
 * @Comments : 导入导出Excel工具类
 */
public class ExcelUitl {
			
	/**
	 * @MethodName : listToExcel(导出Excel)
	 * @Description : 1.导出Excel（导出到浏览器，工作表的大小是2003支持的最大值）
	 * @Description : 2.导出Excel（导出到浏览器，可以自定义工作表的大小）
	 * @param list
	 *            数据源
	 * @param fieldMap
	 *            类的英文属性和Excel中的中文列名的对应关系 如果需要的是引用对象的属性，则英文属性使用类似于EL表达式的格式
	 *            如：list中存放的都是student，student中又有college属性，而我们需要学院名称，则可以这样写
	 *            fieldMap.put("college.collegeName","学院名称")
	 * @param fileName
	 *            文件的名称        
	 * @param sheetName <--可选-->
	 *            工作表的名称              
	 * @param sheetSize <--可选-->
	 *            每个工作表中记录的最大个数
	 * @param response
	 *            使用response可以导出到浏览器
	 * @param request
	 *            使用request处理文件名        
	 * @throws ExcelException
	 */
	public static <T> void listToExcel(List<T> list,LinkedHashMap<String, String> fieldMap,String fileName,
		      HttpServletResponse response,HttpServletRequest request) throws ExcelException {
	      listToExcel(list, fieldMap, fileName,"Sheet",response,request);
    }
	public static <T> void listToExcel(List<T> list,LinkedHashMap<String, String> fieldMap, String fileName,String sheetName,
			      HttpServletResponse response,HttpServletRequest request) throws ExcelException {
		  listToExcel(list, fieldMap, fileName,sheetName, 65535, response,request);
	}

	public static <T> void listToExcel(List<T> list,LinkedHashMap<String, String> fieldMap, String fileName,String sheetName,
			     int sheetSize, HttpServletResponse response,HttpServletRequest request)throws ExcelException {
		// 设置默认文件名为当前时间：年月日时分秒
		//String fileName = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()).toString();		
		//处理文件名
		String fileNameS=processFileName(request,fileName);		
		// 设置response头信息
		response.reset();
	    response.setContentType("application/vnd.ms-excel;charset=utf-8"); // 改成输出excel文件
		response.setHeader("Content-disposition","attachment;filename="+fileNameS+".xls");
		// 创建工作簿并发送到浏览器
		try {
			OutputStream out = response.getOutputStream();
			listToExcel(list, fieldMap, sheetName, sheetSize, out);
		} catch (Exception e) {
			e.printStackTrace();
			// 如果是ExcelException，则直接抛出
			if (e instanceof ExcelException) {
				throw (ExcelException) e;
				// 否则将其它异常包装成ExcelException再抛出
			} else {
				throw new ExcelException("导出Excel失败");
			}
		}
	}
	
	/**
	 * @MethodName : listToExcel(导出Excel)
	 * @Description : 1.导出Excel（可以导出到本地文件系统，也可以导出到浏览器，工作表大小为2003支持的最大值）
	 * @Description : 2.导出Excel（可以导出到本地文件系统，也可以导出到浏览器，可自定义工作表大小）
	 * @param list
	 *            数据源
	 * @param fieldMap
	 *            类的英文属性和Excel中的中文列名的对应关系 如果需要的是引用对象的属性，则英文属性使用类似于EL表达式的格式
	 *            如：list中存放的都是student，student中又有college属性，而我们需要学院名称，则可以这样写
	 *            fieldMap.put("college.collegeName","学院名称")
	 * @param sheetName
	 *            工作表的名称
	 * @param sheetSize <--可选-->
	 *            每个工作表中记录的最大个数
	 * @param out
	 *            导出流
	 * @throws ExcelException
	 */
	public static <T> void listToExcel(List<T> list,LinkedHashMap<String, String> fieldMap, String sheetName,
			      OutputStream out) throws ExcelException {	
		listToExcel(list, fieldMap, sheetName, 65535, out);	
	}	
    public static <T> void listToExcel(List<T> list,LinkedHashMap<String, String> fieldMap, String sheetName,
				           int sheetSize, OutputStream out) throws ExcelException {
			//判断list中是否有条数
    	    if (list.size() == 0 || list == null) {
				throw new ExcelException("数据源中没有任何数据");
			}
    	    //一个sheet中的大小为65535
			if (sheetSize > 65535 || sheetSize < 1) {
				sheetSize = 65535;
			}
			// 创建工作簿并发送到OutputStream指定的地方
			WritableWorkbook wwb;
			try { 
				wwb = Workbook.createWorkbook(out);
				// 因为2003的Excel一个工作表最多可以有65536条记录，除去列头剩下65535条
				// 所以如果记录太多，需要放到多个工作表中，其实就是个分页的过程
				// 1.计算一共有多少个工作表
				double sheetNum = Math.ceil(list.size()/ new Integer(sheetSize).doubleValue());

				// 2.创建相应的工作表，并向其中填充数据
				for (int i = 0; i < sheetNum; i++) {
					// 如果只有一个工作表的情况
					if (1 == sheetNum) {
						//创建工作表
						WritableSheet sheet = wwb.createSheet(sheetName, i);											
						fillSheet(sheet, list, fieldMap, 0, list.size() - 1);
					// 有多个工作表的情况
					} else {
						WritableSheet sheet = wwb.createSheet(sheetName+ (i + 1), i);
						// 获取开始索引和结束索引
						int firstIndex = i * sheetSize;
						int lastIndex = (i + 1) * sheetSize - 1 > list.size() - 1 ? list.size() - 1 : (i + 1) * sheetSize - 1;
						// 填充工作表
						fillSheet(sheet, list, fieldMap, firstIndex, lastIndex);
					}
				}
				wwb.write();
				wwb.close();

			} catch (Exception e) {
				e.printStackTrace();
				// 如果是ExcelException，则直接抛出
				if (e instanceof ExcelException) {
					throw (ExcelException) e;
					// 否则将其它异常包装成ExcelException再抛出
				} else {
					throw new ExcelException("导出Excel失败");
				}
			}

		}

/*
 * <-------------------------------------------辅助的私有方法-------------------------------------------------->
 * <-------------------------------------------辅助的私有方法-------------------------------------------------->
 */
	/**
	 * @MethodName : fillSheet
	 * @Description : 向工作表中填充数据
	 * @param sheet
	 *            工作表
	 * @param list
	 *            数据源
	 * @param fieldMap
	 *            中英文字段对应关系的Map
	 * @param firstIndex
	 *            开始索引
	 * @param lastIndex
	 *            结束索引
	 */
	private static <T> void fillSheet(WritableSheet sheet, List<T> list,LinkedHashMap<String, String> fieldMap, int firstIndex,
			       int lastIndex) throws Exception {

		// 定义存放英文字段名和中文字段名的数组
		String[] enFields = new String[fieldMap.size()];
		String[] cnFields = new String[fieldMap.size()];
		// 填充数组
		int count = 0;
		for (Entry<String, String> entry : fieldMap.entrySet()) {
			//存放英文字段名
			enFields[count] = entry.getKey();
			//存放中文字段名
			cnFields[count] = entry.getValue();
			count++;
		}
		// 填充表头
		for (int i = 0; i < cnFields.length; i++) {
			Label label = new Label(i, 0, cnFields[i]);
			sheet.addCell(label);
		}
		// 填充内容
		int rowNo = 1;
		for (int index = firstIndex; index <= lastIndex; index++) {
			// 获取单个对象
			T item = list.get(index);
			for (int i = 0; i < enFields.length; i++) {
				Object objValue = getFieldValueByNameSequence(enFields[i],item);
				String fieldValue = objValue == null ? "" : objValue.toString();
				Label label = new Label(i, rowNo, fieldValue);
				sheet.addCell(label);
			}
			rowNo++;
		}
		// 设置自动列宽
		setColumnAutoSize(sheet, 5);
	}
	
	/**
	 * @MethodName : getFieldValueByNameSequence
	 * @Description : 根据带路径或不带路径的属性名获取属性值
	 *              即接受简单属性名，如userName等，又接受带路径的属性名，如student.department.name等
	 * 
	 * @param fieldNameSequence
	 *            带路径的属性名或简单属性名
	 * @param o
	 *            对象
	 * @return 属性值
	 * @throws Exception
	 */
	private static Object getFieldValueByNameSequence(String fieldNameSequence, Object o) throws Exception {
		Object value = null;
		// 将fieldNameSequence进行拆分
		String[] attributes = fieldNameSequence.split("\\.");
		if (attributes.length == 1) {
			value = getFieldValueByName(fieldNameSequence, o);
		} else {
			// 根据属性名获取属性对象
			Object fieldObj = getFieldValueByName(attributes[0], o);
			String subFieldNameSequence = fieldNameSequence.substring(fieldNameSequence.indexOf(".") + 1);
			value = getFieldValueByNameSequence(subFieldNameSequence,fieldObj);
		}
		return value;
	}
		
	/**
	 * @MethodName : getFieldValueByName
	 * @Description : 根据字段名获取字段值
	 * @param fieldName
	 *            字段名
	 * @param o
	 *            对象
	 * @return 字段值
	 * @throws Exception 
	 */	
	private static Object getFieldValueByName(String fieldName, Object o)throws Exception {
		Object value = null;
		Field field = getFieldByName(fieldName, o.getClass());
		if (field != null) {
			field.setAccessible(true);
			value = field.get(o);
		} else {
			throw new ExcelException(o.getClass().getSimpleName()+ "类不存在字段名 " + fieldName);
		}

		return value;
	}

	/**
	 * @MethodName : getFieldByName
	 * @Description : 根据字段名获取字段
	 * @param fieldName
	 *            字段名
	 * @param clazz
	 *            包含该字段的类
	 * @return 字段
	 */
	private static Field getFieldByName(String fieldName, Class<?> clazz) {
		// 拿到本类的所有字段
		Field[] selfFields = clazz.getDeclaredFields();
		// 如果本类中存在该字段，则返回
		for (Field field : selfFields) {
			if (field.getName().equals(fieldName)) {
				return field;
			}
		}
		// 否则，查看父类中是否存在此字段，如果有则返回
		Class<?> superClazz = clazz.getSuperclass();
		if (superClazz != null && superClazz != Object.class) {
			return getFieldByName(fieldName, superClazz);
		}
		// 如果本类和父类都没有，则返回空
		return null;
	}
	
	/**
	 * @MethodName : setColumnAutoSize
	 * @Description : 设置工作表自动列宽和首行加粗
	 * @param ws
	 */
	private static void setColumnAutoSize(WritableSheet ws, int extraWith) {
		// 获取本列的最宽单元格的宽度
		for (int i = 0; i < ws.getColumns(); i++) {
			int colWith = 0;
			for (int j = 0; j < ws.getRows(); j++) {
				String content = ws.getCell(i, j).getContents().toString();
				int cellWith = content.length();
				if (colWith < cellWith) {
					colWith = cellWith;
				}
			}
			// 设置单元格的宽度为最宽宽度+额外宽度
			ws.setColumnView(i, colWith + extraWith);
		}

	}

/*
 * <-------------------------------------------导入Excel-------------------------------------------------->
 * <-------------------------------------------导入Excel-------------------------------------------------->
 */

	/**
	 * @MethodName : excelToList(导入Excel)
	 * @Description : 将Excel转化为List
	 * @param in
	 *            ：承载着Excel的输入流
	 * @param sheetIndex
	 *            ：要导入的工作表序号
	 * @param entityClass
	 *            ：List中对象的类型（Excel中的每一行都要转化为该类型的对象）
	 * @param fieldMap
	 *            ：Excel中的中文列头和类的英文属性的对应关系Map
	 * @param uniqueFields
	 *            ：指定业务主键组合（即复合主键），这些列的组合不能重复
	 * @return ：List
	 * @throws ExcelException
	 */
	public static <T> List<T> excelToList(InputStream in, String sheetName,Class<T> entityClass, LinkedHashMap<String, String> fieldMap,
			     String[] uniqueFields) throws ExcelException {
		// 定义要返回的list
		List<T> resultList = new ArrayList<T>();
		try {
			// 根据Excel数据源创建WorkBook
			Workbook wb = Workbook.getWorkbook(in);
			// 获取工作表
			Sheet sheet = wb.getSheet(sheetName);
			// 获取工作表的有效行数
			int realRows = 0;
			for (int i = 0; i < sheet.getRows(); i++) {
				int nullCols = 0;
				for (int j = 0; j < sheet.getColumns(); j++) {
					Cell currentCell = sheet.getCell(j, i);
					if (currentCell == null|| "".equals(currentCell.getContents().toString())){
						nullCols++;
					}
				}
				if (nullCols == sheet.getColumns()) {
					break;
				} else {
					realRows++;
				}
			}

			// 如果Excel中没有数据则提示错误
			if (realRows <= 1) {
				throw new ExcelException("Excel文件中没有任何数据");
			}
			Cell[] firstRow = sheet.getRow(0);
			String[] excelFieldNames = new String[firstRow.length];
			// 获取Excel中的列名
			for (int i = 0; i < firstRow.length; i++) {
				excelFieldNames[i] = firstRow[i].getContents().toString().trim();
			}
			// 判断需要的字段在Excel中是否都存在
			boolean isExist = true;
			List<String> excelFieldList = Arrays.asList(excelFieldNames);
			for (String cnName : fieldMap.keySet()) {
				if (!excelFieldList.contains(cnName)) {
					isExist = false;
					break;
				}
			}
			// 如果有列名不存在，则抛出异常，提示错误
			if (!isExist) {
				throw new ExcelException("Excel中缺少必要的字段，或字段名称有误");
			}
			// 将列名和列号放入Map中,这样通过列名就可以拿到列号
			LinkedHashMap<String, Integer> colMap = new LinkedHashMap<String, Integer>();
			for (int i = 0; i < excelFieldNames.length; i++) {
				colMap.put(excelFieldNames[i], firstRow[i].getColumn());
			}
			// 判断是否有重复行
			// 1.获取uniqueFields指定的列
			Cell[][] uniqueCells = new Cell[uniqueFields.length][];
			for (int i = 0; i < uniqueFields.length; i++) {
				int col = colMap.get(uniqueFields[i]);
				uniqueCells[i] = sheet.getColumn(col);
			}
			// 2.从指定列中寻找重复行
			for (int i = 1; i < realRows; i++) {
				int nullCols = 0;
				for (int j = 0; j < uniqueFields.length; j++) {
					String currentContent = uniqueCells[j][i].getContents();
					/*
					Cell sameCell = sheet.findCell(currentContent,
							uniqueCells[j][i].getColumn(),
							uniqueCells[j][i].getRow() + 1,
							uniqueCells[j][i].getColumn(),
							uniqueCells[j][realRows - 1].getRow(), true);
							*/
					Cell sameCell = sheet.findCell(currentContent);					
					if (sameCell != null) {
						nullCols++;
					}
				}
				if (nullCols == uniqueFields.length) {
					throw new ExcelException("Excel中有重复行，请检查");
				}
			}

			// 将sheet转换为list
			for (int i = 1; i < realRows; i++) {
				// 新建要转换的对象
				T entity = entityClass.newInstance();
				// 给对象中的字段赋值
				for (Entry<String, String> entry : fieldMap.entrySet()) {
					// 获取中文字段名
					String cnNormalName = entry.getKey();
					// 获取英文字段名
					String enNormalName = entry.getValue();
					// 根据中文字段名获取列号
					int col = colMap.get(cnNormalName);
					// 获取当前单元格中的内容
					String content = sheet.getCell(col, i).getContents().toString().trim();
					// 给对象赋值
					setFieldValueByName(enNormalName, content, entity);
				}
				resultList.add(entity);
			}
		} catch (Exception e) {
			e.printStackTrace();
			// 如果是ExcelException，则直接抛出
			if (e instanceof ExcelException) {
				throw (ExcelException) e;
				// 否则将其它异常包装成ExcelException再抛出
			} else {
				e.printStackTrace();
				throw new ExcelException("导入Excel失败");
			}
		}
		return resultList;
	}
	/**
	 * @MethodName : setFieldValueByName
	 * @Description : 根据字段名给对象的字段赋值
	 * @param fieldName
	 *            字段名
	 * @param fieldValue
	 *            字段值
	 * @param o
	 *            对象
	 * @throws Exception 
	 */		
	private static void setFieldValueByName(String fieldName,Object fieldValue, Object o) throws Exception {

		Field field = getFieldByName(fieldName, o.getClass());						
		if (field != null) {
			if ("".equals(fieldValue) || fieldValue==null) {
				field.setAccessible(true);
				field.set(o, fieldValue);				 
		   }else {	
			    field.setAccessible(true);
				// 获取字段类型
				Class<?> fieldType = field.getType();
				// 根据字段类型给字段赋值
				if (String.class == fieldType) {
					field.set(o, String.valueOf(fieldValue));
				} else if ((Integer.TYPE == fieldType) || (Integer.class == fieldType)) {
					field.set(o, Integer.parseInt(fieldValue.toString()));
				} else if ((Long.TYPE == fieldType)
						|| (Long.class == fieldType)) {
					field.set(o, Long.valueOf(fieldValue.toString()));
				} else if ((Float.TYPE == fieldType)
						|| (Float.class == fieldType)) {
					field.set(o, Float.valueOf(fieldValue.toString()));
				} else if ((Short.TYPE == fieldType)
						|| (Short.class == fieldType)) {
					field.set(o, Short.valueOf(fieldValue.toString()));
				} else if ((Double.TYPE == fieldType)
						|| (Double.class == fieldType)) {
					field.set(o, Double.valueOf(fieldValue.toString()));
				} else if (Character.TYPE == fieldType) {
					if ((fieldValue != null)&& (fieldValue.toString().length() > 0)) {
						field.set(o,Character.valueOf(fieldValue.toString().charAt(0)));
					}
				} else if (Date.class == fieldType) {
					field.set(o, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(fieldValue.toString()));
				} else {
					field.set(o, fieldValue);
				}
		  }
		} else {
			throw new ExcelException(o.getClass().getSimpleName()+ "类不存在字段名 " + fieldName);
		}
		
	}
				
	 /**
	 * @Description: ie,chrom,firfox下处理文件名显示乱码 (处理文件名问题,把文件名变成浏览器可识别的)
	  * @param request
	  * @param fileName
	  *           文件名
	  * @return 新的文件名
	  */
	public static String processFileName(HttpServletRequest request, String fileNames) {  
	        String codedfilename = null;  
	        try {  
	            String agent = request.getHeader("USER-AGENT");  
	            if (null != agent && -1 != agent.indexOf("MSIE") || null != agent  
	                    && -1 != agent.indexOf("Trident")) {// ie  
	  
	                String name = java.net.URLEncoder.encode(fileNames, "UTF8");  
	  
	                codedfilename = name;  
	            } else if (null != agent && -1 != agent.indexOf("Mozilla")) {// 火狐,chrome等  
	  
	  
	                codedfilename = new String(fileNames.getBytes("UTF-8"), "iso-8859-1");  
	            }  
	        } catch (Exception e) {  
	            e.printStackTrace();  
	        }  
	        return codedfilename;  
	    } 	

	
	/**
	 * @MethodName : setFieldByValue(对外公开方法)
	 * @Description : 根据字段名给对象的字段赋值
	 * @param fieldName
	 *            字段名
	 * @param fieldValue
	 *            字段值
	 * @param o
	 *            对象
	 * @throws Exception 
	 */
	public static void setFieldByValue(String fieldName,Object fieldValue, Object o) throws Exception{		
		setFieldValueByName(fieldName,fieldValue,o);
	}
	
	
	/**
	 * @MethodName : getFieldByValue(对外公开方法)
	 * @Description : 根据字段名获取字段值
	 * @param fieldName
	 *            字段名
	 * @param clazz
	 *            包含该字段的类
	 * @return 字段
	 */
	public static Object getFieldByValue(String fieldName, Object o) throws Exception{
		return getFieldValueByName(fieldName, o);
	}
	
	/**
	 * 根据字段名获取list中对应的字段值
	 * @param list
	 * @param targetField(要取值的字段名)
	 * @return
	 * @throws Exception 
	 */
	public static <T> List<String> getFieldByAllValue(List<T> lists,String targetField) throws Exception{
		  List<String> stringList=new ArrayList<String>();		  
		  for (Object list : lists) {
			  String value=(String) list.getClass().getDeclaredField(targetField).get(list);
			 stringList.add(value);
		  }		
		  return stringList;
	}
	
 	/**
 	 * 根据指定字段向list中对象的目标字段赋值
 	 * @param list(list中对象)
 	 * @param valueMap(值) <赋值字段,<对应字段的值,赋值字段的值>>
 	 * @param matchingField(对应字段)
 	 * @return
 	 * @throws Exception 
 	 */
 	 public static <T> List<T> setFieldOnValue(List<T> list,Map<String, Map<String, String>> valueMap,String matchingField) throws Exception{		 		 
 		 //创建个新的list,用于存放改变后的对象
		 List<T> newList= new ArrayList<T>();
		 //得到list中对象的class
	     Class<?> listClass=list.get(0).getClass();
	     //得到对象的所有的列
	     Field [] fields=listClass.getDeclaredFields();
	     //循环list中是对象
	       for (int i = 0; i < list.size(); i++) {
	    	     //创建新的对象
				 Object newClass= listClass.newInstance();
				 //得到list对应字段的值
				 Object fieldValue=list.get(i).getClass().getDeclaredField(matchingField).get(list.get(i));
				 //循环所有的列
				 for (Field field : fields) {
					 //判断map中的字段值是否对象中的字段值
					 if (valueMap.containsKey(field.getName())) {
						 //得到字段名
						 String fieldName=field.getName();
						                              //根据字段名从map中取出对应的map,在根据对应字段从对应的map中取出值
						 setFieldValueByName(fieldName,valueMap.get(fieldName).get(fieldValue),newClass);
					 }else {
						 setFieldValueByName(field.getName(),getFieldValueByName(field.getName(),list.get(i)),newClass);
					 }					 
				}
				 newList.add((T)newClass); 
		     }	     		 
		 return newList;
	 } 
			
}


